﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using TO3.DAL;

namespace TO3.Models
{
    public static class MatchMaker
    {
        private static TournamentContext db; // = new TournamentContext();

        /// <summary>
        /// TODO: Remove redundant code from the 'give last player 'bye' win' code below
        /// </summary>
        /// <param name="tournamentId"></param>
        /// <param name="roundNumber"></param>
        public static void CreateMatches(int tournamentId, int roundNumber)
        {
            db = new TournamentContext();

            //Get a list of all players not matched yet (which is all of them right now with a registration for this tournament)
            var TournamentRegistrations = from registrations in db.TournamentRegistrations
                                     where registrations.TournamentId == tournamentId
                                     select registrations.PlayerId;

            var playersNotMatched = (from player in db.Players
                                    where TournamentRegistrations.Contains(player.Id) && player.Name != "Organiser/Freebie"
                           select player).ToList();
                        
            int currentTableNumber = 1;
            Random rand = new Random();
            Player selectedPlayer;
            Player selectedOpponent;
            int selectedOpponentNumber;
            Match match;

            #region roundNumber = 1
            //Check the round number (first round, matches are made based only on location of players)
            if (roundNumber == 1)
            {
                int locationCount = 0;
                int locationCountMax = 0;
                int mostCommonLocationId = 0;

                do
                {
                    //Find the most common location amongst the remaining players
                    
                    //Reset the counters in order to find the (possible new) most common location
                    locationCount = 0;
                    locationCountMax = 0;
                    mostCommonLocationId = 0;

                    var playerLocations = from player in playersNotMatched
                                          select player.Location;

                    foreach (var location in playerLocations)
                    {
                        locationCount = playersNotMatched.Count(p => p.LocationId == location.Id);

                        if (locationCount > locationCountMax)
                        {
                            locationCountMax = locationCount;
                            mostCommonLocationId = location.Id;
                        }
                    }

                    //Select the player we're currently trying to match
                    selectedPlayer = playersNotMatched.First(player => player.LocationId == mostCommonLocationId);

                    //Find all players with a location not equal to this player, these are the eligable opponents for this round
                    var eligiblePlayers = (from player in playersNotMatched
                                            where player.LocationId != selectedPlayer.LocationId
                                            select player).ToList();

                    //If there's at least one eligible player, select one of them.
                    if (eligiblePlayers.Count > 0)
                    {
                        selectedOpponentNumber = rand.Next(0, eligiblePlayers.Count() - 1);
                        selectedOpponent = eligiblePlayers.ElementAt(selectedOpponentNumber);
                    }
                    else //Else, if there's only players left from the same location, choose a random one from the ones remaining
                    {
                        selectedOpponentNumber = rand.Next(0, playersNotMatched.Count() - 1);
                        selectedOpponent = playersNotMatched.ElementAt(selectedOpponentNumber);
                    }

                    match = new Match(tournamentId, selectedPlayer.Id, selectedOpponent.Id, currentTableNumber, roundNumber);

                    db.Matches.Add(match);

                    db.SaveChanges();

                    //Remove the players from the 'not matched' list, since the players have been matched
                    playersNotMatched.Remove(selectedPlayer);
                    playersNotMatched.Remove(selectedOpponent);

                    //And select the next table
                    currentTableNumber++;
                    
                } while (playersNotMatched.Count() > 1);

                //Check if a last player remains, this player will have to get a "bye"
                if (playersNotMatched.Count() > 0)
                {
                    //There's still a single player remaining with no opponent, give this player a free win
                    //In the first round, this player will be a random player from the most common location
                    //TODO: Change this so that the first round free-bie does not always go to a player from the most common location by selecting the player before matching.
                    selectedPlayer = playersNotMatched.ElementAt(0);
                    selectedOpponent = db.Players.First(p => p.Name == "Organiser/Freebie");
                    match = new Match(tournamentId, selectedPlayer.Id, selectedOpponent.Id, currentTableNumber, roundNumber);
                    match.WinnerId = selectedPlayer.Id;
                    match.Player1CP = 5;

                    db.Matches.Add(match);
                    db.SaveChanges();
                }

            }
            #endregion

            #region roundNumber > 1
            else //Round > 1
            {
                //Create a list of the respective players and their place in the rankings

                var matchesInTournament = db.Matches.Where(m => m.TournamentId == tournamentId);

                //Find and sort all players and their respective wins and control points
                var sortedPlayerScoreList = (from p in playersNotMatched
                                             join m in matchesInTournament on p.Id equals m.Player1Id into pm1
                                             join m2 in matchesInTournament on p.Id equals m2.Player2Id into pm2
                                             join m3 in matchesInTournament on p.Id equals m3.WinnerId into pm3
                                       
                                             select new RankingsViewModel { Id = p.Id, Name = p.Name, TotalCP = pm1.Sum(m => m.Player1CP) + pm2.Sum(m2 => m2.Player2CP), Wins = pm3.Count() } into selection
                                             orderby selection.Wins descending, selection.TotalCP descending, rand.Next(1000)
                                             select selection
                                             ).ToList();

                //Clear playersNotMatched, we're using sortedPlayers here instead
                playersNotMatched.Clear();

                //Create matches
                //TODO: test this for uneven number of players!
                do
                {
                    //Select the first player
                    int selectedPlayerId = sortedPlayerScoreList.ElementAt(0).Id;
                    selectedPlayer = db.Players.Single(p => p.Id == selectedPlayerId);

                    //Find his opponent, this would normaly be the player just below him in the rankings but! -
                    //If he already played the player just below him, select the next player below - and so on.

                    //Make a list of all player Id's this player has played against in this tournament
                    List<int> playedPlayersId1 = (from m in db.Matches
                                                  where m.Player1Id == selectedPlayerId && m.TournamentId == tournamentId
                                                  select m.Player2Id).ToList<int>();

                    List<int> playedPlayersId2 = (from m in db.Matches
                                                  where m.Player2Id == selectedPlayerId && m.TournamentId == tournamentId
                                                  select m.Player1Id).ToList<int>();

                    List<int> playedPlayersId = playedPlayersId1.Concat(playedPlayersId2).ToList();
                    
                    //Add the players own Id so that he does not end up playing himself
                    playedPlayersId.Add(selectedPlayerId);

                    //Match the players
                    RankingsViewModel selectedOpponentFromScoreList;
                    
                    try
                    {
                        selectedOpponentFromScoreList = sortedPlayerScoreList.First(p => !playedPlayersId.Contains(p.Id));
                        match = new Match(tournamentId, selectedPlayer.Id, selectedOpponentFromScoreList.Id, currentTableNumber, roundNumber);
                    }
                    //If the player has none left to play, throw the exception since we should not be creating any more matches.
                    catch (System.InvalidOperationException e)
                    {                        
                        throw e;
                    }                   

                    //Create and add the match                    
                    db.Matches.Add(match);

                    //Increment the table number
                    currentTableNumber++;

                    //Remove the players from the list of players not matched
                    //Remove the top player, this is the player we're matching
                    sortedPlayerScoreList.RemoveAt(0);
                    //Now remove his opponent
                    sortedPlayerScoreList.Remove(selectedOpponentFromScoreList);

                } while (sortedPlayerScoreList.Count() > 1);                

                //If there's a single unmatched player left, this player will get a 'bye'
                if (sortedPlayerScoreList.Count() > 0)
                {
                    selectedOpponent = db.Players.First(p => p.Name == "Organiser/Freebie");
                    match = new Match(tournamentId, sortedPlayerScoreList.ElementAt(0).Id, selectedOpponent.Id, currentTableNumber, roundNumber);
                    match.WinnerId = sortedPlayerScoreList.ElementAt(0).Id;
                    match.Player1CP = 5;

                    //Add the match
                    db.Matches.Add(match);
                }

                //Save the changes
                db.SaveChanges();
            }
            #endregion

            //Increment the round number
            var tournament = db.Tournaments.Single(t => t.Id == tournamentId);
            tournament.ActiveRound++;
            db.SaveChanges();
            
        }

    }
}